﻿using VsxFactory.Modeling;
using System.IO;
using Microsoft.VisualStudio.TextTemplating;
using System.CodeDom.Compiler;
using VsxFactory.Modeling.VisualStudio;
using System.Text;
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.VisualStudio.TextTemplating.VSHost;
using Microsoft.VisualStudio.Modeling;
using System.Runtime.Remoting.Messaging;

namespace VsxFactory.Modeling.Strategies
{
    public class T4Session
    {
        public string Namespace { get; set; }
        public string OutputFileName { get; set; }
        public string TemplateContent { get; set; }
        public string TemplateFileName { get; set; }
        public string ModelFileName { get; set; }
        public Encoding Encoding { get; set; }
        public ModelElement CurrentElement { get; set; }
    }

    public abstract class StrategyTextTransformation : TextTransformation
    {
        public T GetService<T>() where T:class
        {
            return Store.GetService<T>();
        }

        public ModelElement CurrentElement
        {
            get
            {
                return (ModelElement)Session["$currentElement$"];
            }
        }

        public override void Initialize()
        {
            base.Initialize();
            Store = CurrentElement.Store;
        }

        protected Store Store { get; private set; }

       public new ITextTemplatingSession Session { get; set; }

        /// <summary>
        /// Gets the solution manager.
        /// </summary>
        /// <value>The solution manager.</value>
        public IVsSolutionExplorer SolutionManager
        {
            get
            {
                return Store.GetService<IVsSolutionExplorer>();
            }
        }

        protected void AddDomainModel(Type modelType)
        {
        }

        /// <summary>
        /// Gets the naming strategy.
        /// </summary>
        /// <value>The naming strategy.</value>
        protected INamingStrategy NamingStrategy
        {
            get
            {
                return GetService<INamingStrategy>();
            }
        }

        /// <summary>
        /// Gets the element.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <returns></returns>
        public T GetElement<T>() where T : ModelElement
        {
            return Store.ElementDirectory.FindElements<T>().FirstOrDefault() as T;
        }

        /// <summary>
        /// </summary>
        /// <param name="disposing"></param>
        protected override void Dispose(bool disposing)
        {
            // On ne veut pas que le store soit détruit car on pointe sur le store courant, on enleve juste la référence
            Store = null;
            base.Dispose(disposing);
        }
    }

    /// <summary>
    /// Host utilisé par le moteur de T4 (Wrapper sur le host courant permettant de donner accés à la session en cours)
    /// </summary>
    public class TextTemplatingEngineHost : ITextTemplatingEngineHost, ITextTemplatingSessionHost
    {
        ITextTemplatingEngineHost _host;
        private T4Session _session;
        private string extension;
        public bool HasErrors { get; private set; }

        /// <summary>
        /// Initializes a new instance of the <see cref="TextTemplatingEngineHost"/> class.
        /// </summary>
        /// <param name="serviceProvider">The service provider.</param>
        /// <param name="session">The session.</param>
        public TextTemplatingEngineHost(IServiceProvider serviceProvider, T4Session session)
        {
            Guard.ArgumentNotNull(session, "session");
            _host = serviceProvider.GetService(typeof(STextTemplating)) as ITextTemplatingEngineHost;
            _session = session;
            ((ITextTemplatingComponents)_host).InputFile = session.TemplateFileName;
            ((ITextTemplating)_host).BeginErrorSession();
        }

        public string GetExtension()
        {
            return extension;
        }


        #region ITextTemplatingEngineHost Wrapper

        /// <summary>
        /// Gets the host option.
        /// </summary>
        /// <param name="optionName">Name of the option.</param>
        /// <returns></returns>
        public object GetHostOption(string optionName)
        {
            return _host.GetHostOption(optionName);
        }

        /// <summary>
        /// Acquires the text that corresponds to a request to include a partial text template file.
        /// </summary>
        /// <param name="requestFileName">The name of the partial text template file to acquire.</param>
        /// <param name="content">A <see cref="T:System.String"/> that contains the acquired text or <see cref="F:System.String.Empty"/> if the file could not be found.</param>
        /// <param name="location">A <see cref="T:System.String"/> that contains the location of the acquired text. If the host searches the registry for the location of include files or if the host searches multiple locations by default, the host can return the final path of the include file in this parameter. The host can set the <paramref name="location"/> to <see cref="F:System.String.Empty"/> if the file could not be found or if the host is not file-system based.</param>
        /// <returns>
        /// true to indicate that the host was able to acquire the text; otherwise, false.
        /// </returns>
        public bool LoadIncludeText(string requestFileName, out string content, out string location)
        {
            return _host.LoadIncludeText(requestFileName, out content, out location);
        }

        /// <summary>
        /// Receives a collection of errors and warnings from the transformation engine.
        /// </summary>
        /// <param name="errors">The <see cref="T:System.CodeDom.Compiler.CompilerErrorCollection"/>  being passed to the host from the engine.</param>
        public void LogErrors(CompilerErrorCollection errors)
        {
            this.HasErrors |= errors.HasErrors;
            _host.LogErrors(errors);
        }

        /// <summary>
        /// Provides an application domain to run the generated transformation class.
        /// </summary>
        /// <param name="content">The contents of the text template file to be processed.</param>
        /// <returns>
        /// An <see cref="T:System.AppDomain"/> that compiles and executes the generated transformation class.
        /// </returns>
        public AppDomain ProvideTemplatingAppDomain(string content)
        {
            return AppDomain.CurrentDomain;
        }

        /// <summary>
        /// Allows a host to provide additional information about the location of an assembly.
        /// </summary>
        /// <param name="assemblyReference">The assembly to resolve.</param>
        /// <returns>
        /// A <see cref="T:System.String"/> that contains the specified assembly reference or the specified assembly reference with additional information.
        /// </returns>
        public string ResolveAssemblyReference(string assemblyReference)
        {
            if (String.Compare(assemblyReference, VsxFactoryConstants.Name + ".Modeling", true) == 0)
                return this.GetType().Assembly.Location;
            return _host.ResolveAssemblyReference(assemblyReference);
        }

        /// <summary>
        /// Returns the type of a directive processor, given its friendly name.
        /// </summary>
        /// <param name="processorName">The name of the directive processor to be resolved.</param>
        /// <returns>
        /// The <see cref="T:System.Type"/> of the directive processor.
        /// </returns>
        public Type ResolveDirectiveProcessor(string processorName)
        {
            return _host.ResolveDirectiveProcessor(processorName);
        }

        /// <summary>
        /// Resolves the value of a parameter for a directive processor if the parameter is not specified in the template text.
        /// </summary>
        /// <param name="directiveId">The ID of the directive call to which the parameter belongs. This ID disambiguates repeated calls to the same directive from the same text template.</param>
        /// <param name="processorName">The name of the directive processor to which the directive belongs.</param>
        /// <param name="parameterName">The name of the parameter to be resolved.</param>
        /// <returns>
        /// A <see cref="T:System.String"/> that represents the resolved parameter value.
        /// </returns>
        public string ResolveParameterValue(string directiveId, string processorName, string parameterName)
        {
            return _host.ResolveParameterValue(directiveId, processorName, parameterName);
        }

        /// <summary>
        /// Resolves the path.
        /// </summary>
        /// <param name="path">The path.</param>
        /// <returns></returns>
        public string ResolvePath(string path)
        {
            return _host.ResolvePath(path);
        }

        /// <summary>
        /// Tells the host the file name extension that is expected for the generated text output.
        /// </summary>
        /// <param name="extension">The file name extension for the generated text output.</param>
        public void SetFileExtension(string extension)
        {
            this.extension = extension;
            _host.SetFileExtension(extension);
        }

        /// <summary>
        /// Tells the host the encoding that is expected for the generated text output.
        /// </summary>
        /// <param name="encoding">The encoding for the generated text output.</param>
        /// <param name="fromOutputDirective">true to indicate that the user specified the encoding in the encoding parameter of the output directive.</param>
        public void SetOutputEncoding(Encoding encoding, bool fromOutputDirective)
        {
            _host.SetOutputEncoding(encoding, fromOutputDirective);
        }

        /// <summary>
        /// Gets a list of assembly references.
        /// </summary>
        /// <value></value>
        /// <returns>
        /// An <see cref="T:System.Collections.IList"/> that contains assembly names.
        /// </returns>
        public IList<string> StandardAssemblyReferences
        {
            get
            {
                var list = new List<string>();
                list.AddRange(_host.StandardAssemblyReferences);
                list.Add(typeof(ModelElement).Assembly.Location);
                return list;
            }
        }

        /// <summary>
        /// Gets a list of namespaces.
        /// </summary>
        /// <value></value>
        /// <returns>
        /// An <see cref="T:System.Collections.IList"/> that contains namespaces.
        /// </returns>
        public IList<string> StandardImports
        {
            get
            {
                var list = new List<string>();
                list.AddRange(_host.StandardImports);
                return list;
            }
        }

        /// <summary>
        /// Gets the path and file name of the text template that is being processed.
        /// </summary>
        /// <value></value>
        /// <returns>
        /// A <see cref="T:System.String"/> that contains the path and file name of the text template that is being processed.
        /// </returns>
        public string TemplateFile
        {
            get { return _session.TemplateFileName; }
        }

        #endregion

        ITextTemplatingSession ITextTemplatingSessionHost.CreateSession()
        {
            var s = new TextTemplatingSession();
            if( _session.CurrentElement != null)
                s["$currentElement$"] = _session.CurrentElement;
            return s;
        }

        ITextTemplatingSession ITextTemplatingSessionHost.Session { get { return ((ITextTemplatingSessionHost)this).CreateSession(); } set { } }

        internal void EndErrorSession()
        {
            ((ITextTemplating)_host).EndErrorSession();
        }
    }


    /// <summary>
    /// Class de base pour le moteur T4. 
    /// </summary>
    public class T4Engine
    {
        IServiceProvider _serviceprovider;

        /// <summary>
        /// Initializes a new instance of the <see cref="T4Helper"/> class.
        /// </summary>
        /// <param name="serviceProvider">The service provider.</param>
        public T4Engine(IServiceProvider serviceProvider)
        {
            Guard.ArgumentNotNull(serviceProvider, "serviceProvider");

            _serviceprovider = serviceProvider;
        }

        /// <summary>
        /// Runs the template.
        /// </summary>
        /// <param name="session">The session.</param>
        /// <returns></returns>
        public bool RunTemplate(T4Session session)
        {
            if (session.Encoding == null)
                session.Encoding = Encoding.UTF8;

            Engine engine = new Engine();
            var host = new TextTemplatingEngineHost(_serviceprovider, session);
            try
            {
                CallContext.LogicalSetData("NamespaceHint", session.Namespace);

                string data = engine.ProcessTemplate(session.TemplateContent, host);
                if (String.IsNullOrEmpty(session.OutputFileName))
                    return false;
                if (String.IsNullOrEmpty(data) || host.HasErrors)
                    return false;

                var ext = host.GetExtension();
                // Si un extension est forcée dans le template, on en tient compte.
                if (ext != null)
                    session.OutputFileName = Path.ChangeExtension(session.OutputFileName, ext);
                Directory.CreateDirectory(Path.GetDirectoryName(session.OutputFileName));
                File.WriteAllText(session.OutputFileName, data, Encoding.UTF8);
                return true;
            }
            catch (Exception ex)
            {
                CompilerErrorCollection errors = new CompilerErrorCollection();
                errors.Add(new CompilerError(session.ModelFileName, -1, -1, "T40001", ex.Message));
                host.LogErrors(errors);
            }
            finally
            {
                CallContext.FreeNamedDataSlot("NamespaceHint");
                host.EndErrorSession();
            }
            return false;
        }

    }
}